/*
silverPDF is sponsored by Aleyant Systems (http://www.aleyant.com)

silverPDF is based on PdfSharp (http://www.pdfsharp.net) and iTextSharp (http://itextsharp.sourceforge.net)

Developers: Ai_boy (aka Oleksii Okhrymenko)

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above information and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR SPONSORS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

*/
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
//   Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
//
// Copyright (c) 2005-2008 empira Software GmbH, Cologne (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

#define VERBOSE_

using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
#if WPF
using System.Windows.Media;
#endif
using PdfSharp.Drawing;
using PdfSharp.Internal;

using Fixed = System.Int32;
using FWord = System.Int16;
using UFWord = System.UInt16;
using silverPDF._compatebleLayer;

namespace PdfSharp.Fonts.TrueType
{
  /// <summary>
  /// Represents a TrueType font in memory.
  /// </summary>
  class FontData
  {
    public FontData(FontData fontData)
    {
      this.offsetTable = fontData.offsetTable;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="FontData"/> class.
    /// </summary>
    public FontData(XFont font, XPdfFontOptions options)
    {
#if GDI && !WPF
      CreateGdiFontImage(font, options, font.privateFontCollection);
#endif
#if WPF && !GDI
        CreateWpfFontData(font, options);
#endif
#if WPF && GDI
      System.Drawing.Font gdiFont = font.RealizeGdiFont();
      if (font.font != null)
        CreateGdiFontImage(font, options, font.privateFontCollection);
      else if (font.typeface != null)
        CreateWpfFontData(font, options);
#endif
        if (this.data == null)
            throw new InvalidOperationException("Cannot allocate font image.");
        Read();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="FontData"/> class.
    /// </summary>
    public FontData(byte[] fontData)
    {
      this.data = fontData;
      Read();
    }

#if GDI
    /// <summary>
    /// Create the font image using GDI+ functionality.
    /// </summary>
    void CreateGdiFontImage(XFont font, XPdfFontOptions options, XPrivateFontCollection privateFontCollection)
    {
      System.Drawing.Font gdiFont = font.RealizeGdiFont();
      NativeMethods.LOGFONT logFont;
#if DEBUG_
      logFont = new NativeMethods.LOGFONT();
      gdiFont.ToLogFont(logFont);
      Debug.WriteLine("FontData: " + logFont.lfFaceName);
#endif
      this.data = null;
      if (privateFontCollection != null)
      {
        //XPrivateFont privateFont = privateFontCollection.FindFont(logFont.lfFaceName, logFont.lfWeight >= 700, logFont.lfItalic > 0);
        XPrivateFont privateFont = privateFontCollection.FindFont(font.Name, font.Bold, font.Italic);
        if (privateFont != null)
        {
          int size = privateFont.GetFontData(ref this.data, 0);
          if (size > 0)
          {
            this.data = new byte[size];
            privateFont.GetFontData(ref this.data, size);
          }
        }
      }
      if (this.data == null)
      {
        int error;
        IntPtr hfont = gdiFont.ToHfont();
        IntPtr hdc = NativeMethods.GetDC(IntPtr.Zero);
        error = Marshal.GetLastWin32Error();
        IntPtr oldFont = NativeMethods.SelectObject(hdc, hfont);
        error = Marshal.GetLastWin32Error();
        // size is exactly the size of the font file.
        int size = NativeMethods.GetFontData(hdc, 0, 0, null, 0);
        error = Marshal.GetLastWin32Error();
        if (size > 0)
        {
          this.data = new byte[size];
          int effectiveSize = NativeMethods.GetFontData(hdc, 0, 0, this.data, this.data.Length);
          Debug.Assert(size == effectiveSize);
          NativeMethods.SelectObject(hdc, oldFont);
          NativeMethods.ReleaseDC(IntPtr.Zero, hdc);
          error.GetType();
        }
        else
        {
        // Sometimes size is -1 (GDI_ERROR), but I cannot determine why. It happens only with the font 'Symbol'.
        // The issue occurs the first time in early 2005, when I start writing PDFsharp. I could not fix it and after
        // some code refactoring the problem disappears.
        // There was never a report from anyone about this issue.
        // Now I get it again (while debugging QBX 2006). Maybe it is a problem with my PC at my home office.
        // As a work-around I create a new font handle with a different height value. This works. Maybe the
        // font file gets locked somewhere. Very very strange.

        // IF SOMEONE ELSE COMES HERE PLEASE LET ME KNOW!

        // Clean up old handles
        NativeMethods.SelectObject(hdc, oldFont);
        NativeMethods.ReleaseDC(IntPtr.Zero, hdc);

        // Try again with new font handle
        logFont = new NativeMethods.LOGFONT();
        gdiFont.ToLogFont(logFont);
        logFont.lfHeight += 1; // force new handle
        IntPtr hfont2 = NativeMethods.CreateFontIndirect(logFont);
        hdc = NativeMethods.GetDC(IntPtr.Zero);
        error = Marshal.GetLastWin32Error();
        oldFont = NativeMethods.SelectObject(hdc, hfont2);
        error = Marshal.GetLastWin32Error();
        // size is exactly the size of the font file.
        size = NativeMethods.GetFontData(hdc, 0, 0, null, 0);
        error = Marshal.GetLastWin32Error();
        if (size > 0)
        {
          this.data = new byte[size];
          int effectiveSize = NativeMethods.GetFontData(hdc, 0, 0, this.data, this.data.Length);
          Debug.Assert(size == effectiveSize);
        }
        NativeMethods.SelectObject(hdc, oldFont);
        NativeMethods.ReleaseDC(IntPtr.Zero, hdc);
        NativeMethods.DeleteObject(hfont2);
        error.GetType();
        }
      }
      if (this.data == null)
        throw new InvalidOperationException("Internal error. Font data could not retrieved.");
    }
#endif

#if WPF
    /// <summary>
    /// Create the font image using WPF functionality.
    /// </summary>
    void CreateWpfFontData(XFont font, XPdfFontOptions options)
    {
      GlyphTypeface glyphTypeface;
      if (!font.typeface.TryGetGlyphTypeface(out glyphTypeface))
        throw new InvalidOperationException(PSSR.CannotGetGlyphTypeface(font.Name));

      Stream fontStream = null;
      try
      {
        fontStream = glyphTypeface.GetFontStream();
        int size = (int)fontStream.Length;
        Debug.Assert(size > 0);
        this.data = new byte[size];
        fontStream.Read(this.data, 0, size);
      }
      finally
      {
        //if (fontStream != null)
          //fontStream.Close();
      }
    }
#endif

    /// <summary>
    /// Gets the bytes that represents the font data.
    /// </summary>
    public byte[] Data
    {
      get { return this.data; }
    }
    byte[] data;

    internal OffsetTable offsetTable;

    /// <summary>
    /// The dictionary of all font tables.
    /// </summary>
    internal Dictionary<string, TableDirectoryEntry> tableDictionary = new Dictionary<string, TableDirectoryEntry>();

    internal CMapTable cmap;
    internal ControlValueTable cvt;
    internal FontProgram fpgm;
    internal MaximumProfileTable maxp;
    internal NameTable name;
    internal ControlValueProgram prep;
    internal FontHeaderTable head;
    internal HorizontalHeaderTable hhea;
    internal HorizontalMetricsTable hmtx;
    internal OS2Table os2;
    internal PostScriptTable post;
    internal GlyphDataTable glyf;
    internal IndexToLocationTable loca;
    internal GlyphSubstitutionTable gsub;

    public bool CanRead
    {
      get { return this.data != null; }
    }

    public bool CanWrite
    {
      get { return this.data == null; }
    }

    /// <summary>
    /// Adds the specified table to this font image.
    /// </summary>
    public void AddTable(TrueTypeFontTable fontTable)
    {
      if (!CanWrite)
        throw new InvalidOperationException("Font image cannot be modified.");

      if (fontTable == null)
        throw new ArgumentNullException("fontTable");

      if (fontTable.fontData == null)
      {
        fontTable.fontData = this;
      }
      else
      {
        Debug.Assert(fontTable.fontData.CanRead);
        // Create a reference to this font table
        fontTable = new IRefFontTable(this, fontTable);
      }

      //Debug.Assert(fontTable.FontData == null);
      //fontTable.fontData = this;

      this.tableDictionary[fontTable.DirectoryEntry.Tag] = fontTable.DirectoryEntry;
      switch (fontTable.DirectoryEntry.Tag)
      {
        case TableTagNames.CMap:
          this.cmap = fontTable as CMapTable;
          break;

        case TableTagNames.Cvt:
          this.cvt = fontTable as ControlValueTable;
          break;

        case TableTagNames.Fpgm:
          this.fpgm = fontTable as FontProgram;
          break;

        case TableTagNames.MaxP:
          this.maxp = fontTable as MaximumProfileTable;
          break;

        case TableTagNames.Name:
          this.name = fontTable as NameTable;
          break;

        case TableTagNames.Head:
          this.head = fontTable as FontHeaderTable;
          break;

        case TableTagNames.HHea:
          this.hhea = fontTable as HorizontalHeaderTable;
          break;

        case TableTagNames.HMtx:
          this.hmtx = fontTable as HorizontalMetricsTable;
          break;

        case TableTagNames.OS2:
          this.os2 = fontTable as OS2Table;
          break;

        case TableTagNames.Post:
          this.post = fontTable as PostScriptTable;
          break;

        case TableTagNames.Glyf:
          this.glyf = fontTable as GlyphDataTable;
          break;

        case TableTagNames.Loca:
          this.loca = fontTable as IndexToLocationTable;
          break;

        case TableTagNames.GSUB:
          this.gsub = fontTable as GlyphSubstitutionTable;
          break;

        case TableTagNames.Prep:
          this.prep = fontTable as ControlValueProgram;
          break;
      }
    }

    /// <summary>
    /// Reads all required tables from the font data.
    /// </summary>
    internal void Read()
    {
      try
      {
        // Read offset table
        this.offsetTable.Version = ReadULong();
        this.offsetTable.TableCount = ReadUShort();
        this.offsetTable.SearchRange = ReadUShort();
        this.offsetTable.EntrySelector = ReadUShort();
        this.offsetTable.RangeShift = ReadUShort();

        // Move to table dictionary at position 12
        Debug.Assert(this.pos == 12);
        //this.tableDictionary = (this.offsetTable.TableCount);
        for (int idx = 0; idx < this.offsetTable.TableCount; idx++)
        {
          TableDirectoryEntry entry = TableDirectoryEntry.ReadFrom(this);

          try
          {
              this.tableDictionary.Add(entry.Tag, entry);
          }
          catch { }

          //Debug.WriteLine(String.Format("Font table: {0}", entry.Tag));

        }

        // PDFlib checks this, but it is not part of the OpenType spec anymore
        if (this.tableDictionary.ContainsKey("bhed"))
          throw new NotSupportedException("Bitmap fonts are not supported by PDFsharp.");

        // Read required tables
        if (Seek(CMapTable.Tag) != -1)
          this.cmap = new CMapTable(this);

        if (Seek(ControlValueTable.Tag) != -1)
          this.cvt = new ControlValueTable(this);

        if (Seek(FontProgram.Tag) != -1)
          this.fpgm = new FontProgram(this);

        if (Seek(MaximumProfileTable.Tag) != -1)
          this.maxp = new MaximumProfileTable(this);

        if (Seek(NameTable.Tag) != -1)
          this.name = new NameTable(this);

        if (Seek(FontHeaderTable.Tag) != -1)
          this.head = new FontHeaderTable(this);

        if (Seek(HorizontalHeaderTable.Tag) != -1)
          this.hhea = new HorizontalHeaderTable(this);

        if (Seek(HorizontalMetricsTable.Tag) != -1)
          this.hmtx = new HorizontalMetricsTable(this);

        if (Seek(OS2Table.Tag) != -1)
          this.os2 = new OS2Table(this);

        if (Seek(PostScriptTable.Tag) != -1)
          this.post = new PostScriptTable(this);

        if (Seek(GlyphDataTable.Tag) != -1)
          this.glyf = new GlyphDataTable(this);

        if (Seek(IndexToLocationTable.Tag) != -1)
          this.loca = new IndexToLocationTable(this);

        if (Seek(GlyphSubstitutionTable.Tag) != -1)
          this.gsub = new GlyphSubstitutionTable(this);

        if (Seek(ControlValueProgram.Tag) != -1)
          this.prep = new ControlValueProgram(this);
      }
      catch (Exception ex)
      {
        throw ex;
      }
    }

    /// <summary>
    /// Creates a new font image that is a subset of this font image containing only the specified glyphs.
    /// </summary>
    public FontData CreateFontSubSet(Dictionary<int, object> glyphs, bool cidFont)
    {
      // Create new font image
      FontData fontData = new FontData(this);

      // Create new loca and glyf table
      IndexToLocationTable loca = new IndexToLocationTable();
      loca.ShortIndex = this.loca.ShortIndex;
      GlyphDataTable glyf = new GlyphDataTable();

      // Add all required tables
      //fontData.AddTable(this.os2);
      if (!cidFont)
        fontData.AddTable(this.cmap);
      if (this.cvt != null)
        fontData.AddTable(this.cvt);
      if (this.fpgm != null)
        fontData.AddTable(this.fpgm);
      fontData.AddTable(glyf);
      fontData.AddTable(this.head);
      fontData.AddTable(this.hhea);
      fontData.AddTable(this.hmtx);
      fontData.AddTable(loca);
      if (this.maxp != null)
        fontData.AddTable(this.maxp);
      //fontData.AddTable(this.name);
      if (this.prep != null)
        fontData.AddTable(this.prep);

      // Get closure of used glyphs
      this.glyf.CompleteGlyphClosure(glyphs);

      // Create a sorted array of all used glyphs
      int glyphCount = glyphs.Count;
      int[] glyphArray = new int[glyphCount];
      glyphs.Keys.CopyTo(glyphArray, 0);
      Array.Sort<int>(glyphArray);

      // Calculate new size of glyph table.
      int size = 0;
      for (int idx = 0; idx < glyphCount; idx++)
        size += this.glyf.GetGlyphSize(glyphArray[idx]);
      glyf.DirectoryEntry.Length = size;

      // Create new loca table
      int numGlyphs = this.maxp.numGlyphs;
      loca.locaTable = new int[numGlyphs + 1];

      // Create new glyf table
      glyf.glyphTable = new byte[glyf.DirectoryEntry.PaddedLength];

      // Fill new glyf and loca table
      int glyphOffset = 0;
      int glyphIndex = 0;
      for (int idx = 0; idx < numGlyphs; idx++)
      {
        loca.locaTable[idx] = glyphOffset;
        if (glyphIndex < glyphCount && glyphArray[glyphIndex] == idx)
        {
          glyphIndex++;
          byte[] bytes = this.glyf.GetGlyphData(idx);
          int length = bytes.Length;
          if (length > 0)
          {
            Buffer.BlockCopy(bytes, 0, glyf.glyphTable, glyphOffset, length);
            glyphOffset += length;
          }
        }
      }
      loca.locaTable[numGlyphs] = glyphOffset;

      // Compile font tables into byte array
      fontData.Compile();

      return fontData;
    }

    /// <summary>
    /// Compiles the font to its binary representation.
    /// </summary>
    void Compile()
    {
      MemoryStream stream = new MemoryStream();
      TrueTypeFontWriter writer = new TrueTypeFontWriter(stream);

      int tableCount = this.tableDictionary.Count;
      int selector = entrySelectors[tableCount];

      this.offsetTable.Version = 0x00010000;
      this.offsetTable.TableCount = tableCount;
      this.offsetTable.SearchRange = (ushort)((1 << selector) * 16);
      this.offsetTable.EntrySelector = (ushort)selector;
      this.offsetTable.RangeShift = (ushort)((tableCount - (1 << selector)) * 16);
      this.offsetTable.Write(writer);

      // Sort tables by tag name
      string[] tags = new string[tableCount];
      this.tableDictionary.Keys.CopyTo(tags, 0);
      Array.Sort<string>(tags, StringComparer.Ordinal);

#if VERBOSE
      Debug.WriteLine("Start Compile");
#endif
      // Write tables in alphabetical order
      int tablePosition = 12 + 16 * tableCount;
      for (int idx = 0; idx < tableCount; idx++)
      {
        TableDirectoryEntry entry = this.tableDictionary[tags[idx]];
#if DEBUG
        if (entry.Tag == "glyf" || entry.Tag == "loca")
          GetType();
#endif
        entry.FontTable.PrepareForCompilation();
        entry.Offset = tablePosition;
        writer.Position = tablePosition;
        entry.FontTable.Write(writer);
        int endPosition = writer.Position;
        tablePosition = endPosition;
        writer.Position = 12 + 16 * idx;
        entry.Write(writer);
#if VERBOSE
        Debug.WriteLine(String.Format("  Write Table '{0}', offset={1}, length={2}, checksum={3}, ", entry.Tag, entry.Offset, entry.Length, entry.CheckSum));
#endif
      }
#if VERBOSE
      Debug.WriteLine("End Compile");
#endif
      writer.Stream.Flush();
      int l = (int)writer.Stream.Length;
      l.GetType();
      this.data = stream.ToArray();
    }
    // 2^entrySelector[n] <= n
    static int[] entrySelectors = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 };

    public int Position
    {
      get { return this.pos; }
      set { this.pos = value; }
    }
    int pos;

    //public int Seek(int position)
    //{
    //  this.pos = position;
    //  return this.pos;
    //}

    public int Seek(string tag)
    {
      if (this.tableDictionary.ContainsKey(tag))
      {
        this.pos = this.tableDictionary[tag].Offset;
        return this.pos;
      }
      return -1;
    }

    public int SeekOffset(int offset)
    {
      this.pos += offset;
      return this.pos;
    }

    /// <summary>
    /// Reads a System.Byte.
    /// </summary>
    public byte ReadByte()
    {
      return this.data[this.pos++];
    }

    /// <summary>
    /// Reads a System.Int16.
    /// </summary>
    public short ReadShort()
    {
      int pos = this.pos;
      this.pos += 2;
      return (short)((this.data[pos] << 8) | (this.data[pos + 1]));
    }

    /// <summary>
    /// Reads a System.UInt16.
    /// </summary>
    public ushort ReadUShort()
    {
      int pos = this.pos;
      this.pos += 2;
      return (ushort)((this.data[pos] << 8) | (this.data[pos + 1]));
    }

    /// <summary>
    /// Reads a System.Int32.
    /// </summary>
    public int ReadLong()
    {
      int pos = this.pos;
      this.pos += 4;
      return (int)((this.data[pos] << 24) | (this.data[pos + 1] << 16) | (this.data[pos + 2] << 8) | (this.data[pos + 3]));
    }

    /// <summary>
    /// Reads a System.UInt32.
    /// </summary>
    public uint ReadULong()
    {
      int pos = this.pos;
      this.pos += 4;
      return (uint)((this.data[pos] << 24) | (this.data[pos + 1] << 16) | (this.data[pos + 2] << 8) | (this.data[pos + 3]));
    }

    /// <summary>
    /// Reads a System.Int32.
    /// </summary>
    public Fixed ReadFixed()
    {
      int pos = this.pos;
      this.pos += 4;
      return (int)((this.data[pos] << 24) | (this.data[pos + 1] << 16) | (this.data[pos + 2] << 8) | (this.data[pos + 3]));
    }

    /// <summary>
    /// Reads a System.Int16.
    /// </summary>
    public short ReadFWord()
    {
      int pos = this.pos;
      this.pos += 2;
      return (short)((this.data[pos] << 8) | (this.data[pos + 1]));
    }

    /// <summary>
    /// Reads a System.UInt16.
    /// </summary>
    public ushort ReadUFWord()
    {
      int pos = this.pos;
      this.pos += 2;
      return (ushort)((this.data[pos] << 8) | (this.data[pos + 1]));
    }

    /// <summary>
    /// Reads a System.Int64.
    /// </summary>
    public long ReadLongDate()
    {
      int pos = this.pos;
      this.pos += 8;
      return (int)((this.data[pos] << 56) | (this.data[pos + 1] << 48) | (this.data[pos + 2] << 40) | (this.data[pos + 32]) |
                   (this.data[pos + 4] << 24) | (this.data[pos + 5] << 16) | (this.data[pos + 5] << 8) | (this.data[pos + 7]));
    }

    /// <summary>
    /// Reads a System.String with the specified size.
    /// </summary>
    public string ReadString(int size)
    {
      char[] chars = new char[size];
      for (int idx = 0; idx < size; idx++)
        chars[idx] = (char)this.data[this.pos++];
      return new string(chars);
    }

    /// <summary>
    /// Reads a System.Byte[] with the specified size.
    /// </summary>
    public byte[] ReadBytes(int size)
    {
      byte[] bytes = new byte[size];
      for (int idx = 0; idx < size; idx++)
        bytes[idx] = this.data[this.pos++];
      return bytes;
    }

    /// <summary>
    /// Reads the specified buffer.
    /// </summary>
    public void Read(byte[] buffer)
    {
      Read(buffer, 0, buffer.Length);
    }

    /// <summary>
    /// Reads the specified buffer.
    /// </summary>
    public void Read(byte[] buffer, int offset, int length)
    {
      Buffer.BlockCopy(this.data, this.pos, buffer, offset, length);
      this.pos += length;
    }

    /// <summary>
    /// Reads a System.Char[4] as System.String.
    /// </summary>
    public string ReadTag()
    {
      return ReadString(4);
    }

    /// <summary>
    /// Represents the font offset table.
    /// </summary>
    internal struct OffsetTable
    {
      /// <summary>
      /// 0x00010000 for version 1.0.
      /// </summary>
      public uint Version;

      /// <summary>
      /// Number of tables.
      /// </summary>
      public int TableCount;

      /// <summary>
      /// (Maximum power of 2 ≤ numTables) x 16.
      /// </summary>
      public ushort SearchRange;

      /// <summary>
      /// Log2(maximum power of 2 ≤ numTables).
      /// </summary>
      public ushort EntrySelector;

      /// <summary>
      /// NumTables x 16-searchRange.
      /// </summary>
      public ushort RangeShift;

      /// <summary>
      /// Writes the offset table.
      /// </summary>
      public void Write(TrueTypeFontWriter writer)
      {
        writer.WriteUInt(Version);
        writer.WriteShort(TableCount);
        writer.WriteUShort(SearchRange);
        writer.WriteUShort(EntrySelector);
        writer.WriteUShort(RangeShift);
      }
    }
  }
}